package procsim;

import java.util.*;

public class MSignal implements Sig, Updateable {
    public static final int HIGHZ = -1;
    private int numOfBits, val;
    private Signal[] bits;
    private String name;
    private ArrayList<Updateable> elements;
    private LinkedHashMap<Sig, Integer> insigs, outsigs;
    private void update(boolean apply){
        if(val==HIGHZ){
            if(apply && elements!=null) applySignalToElements();
            return;
        }
        int[] array=Util.int2BinaryArray(val, numOfBits);
        if(insigs!=null){
            int newval = 0;
            Iterator iter = insigs.keySet().iterator();
            while(iter.hasNext()){
                Sig sig = (Sig)iter.next();
                if(sig instanceof Signal){
                    Signal ssig = (Signal)sig;
                    int index = insigs.get(ssig);
                    newval |= (sig.get() & 1) << index;
                } else {
                    MSignal msig = (MSignal)sig;
                    int sindex = insigs.get(msig);
                    int length = msig.getNumOfBits();
                    int mask = ((1<<length)-1);
                    newval |= (msig.get() & mask) << sindex;
                }
            }
            val = newval;
            array=Util.int2BinaryArray(val, numOfBits);
        }
        bits=Util.intArray2SignalArray(array);
        val=Util.binaryArray2Int(array);
        if(outsigs!=null){
            Iterator iter = outsigs.keySet().iterator();
            while(iter.hasNext()){
                Sig sig = (Sig)iter.next();
                if(sig instanceof Signal){
                    Signal ssig = (Signal)sig;
                    ssig.set(bits[outsigs.get(ssig)]);
                } else {
                    MSignal msig = (MSignal)sig;
                    int sindex = outsigs.get(msig);
                    int length = msig.getNumOfBits();
                    int mask = ((1<<length)-1)<<sindex;
                    int newval = (val&mask)>>sindex;
                    msig.set(newval);
                }
            }
        }
        if(apply && elements!=null) applySignalToElements();
    }
    public MSignal(int numOfBits, int vrednost){
        this.numOfBits=numOfBits;
        val=vrednost;
        bits = new Signal[numOfBits];
        update(true);
    }
    public MSignal(int numOfBits, int vrednost, String nam){
        this(numOfBits, vrednost);
        name=nam;
    }
    public MSignal(Signal sig){
        this(1, sig.get());
    }
    public MSignal(Signal sig, String nam){
        this(1, sig.get(), nam);
    }
    public MSignal(Signal[] sig){
        this(sig.length, Util.binaryArray2Int(Util.signalArray2IntArray(sig)));
    }
    public MSignal(Signal[] sig, String nam){
        this(sig.length, Util.binaryArray2Int(Util.signalArray2IntArray(sig)), nam);
    }
    @Override
    public int get(){
        return val;
    }
    public Signal[] getBits(){
        return bits;
    }
    public Signal getBit(int index){
        if(index>=0 && index<numOfBits)
            return bits[index];
        else {
            System.err.println("Pristup nepostojecem bitu neuspesan!");
            return null;
        }
    }
    @Override
    public MSignal not(){
        if(val==HIGHZ) val=0;
        else if(numOfBits==1) val=Math.abs(val-1);
        else val=(~val)%(2^numOfBits);
        update(true);
        return this;
    }
    public MSignal negate(){
        return new MSignal(numOfBits, ~val);
    }
    @Override
    public String toString(){
        return name+":"+Util.int2Hex(val, numOfBits);
    }
    @Override
    public String getName(){
        return name;
    }
    @Override
    public MSignal setName(String str){
        name = str;
        return this;
    }
    @Override
    public boolean isNull(){
        return val==0;
    }
    public int getNumOfBits(){
        return numOfBits;
    }
    public MSignal set(MSignal sig){
        return set(sig, true);
    }
    public MSignal set(int sig){
        return set(sig, true);
    }
    public MSignal set(MSignal sig, boolean apply){
        if(sig==null || val==sig.get()) return this;
        val=sig.get();
        numOfBits=sig.getNumOfBits();
        update(apply);
        return this;
    }
    public MSignal set(int sig, boolean apply){
        if(val==sig) return this;
        val=sig;
        update(apply);
        return this;
    }
    public MSignal bindOut(int index, Signal sig){
        if(index>=0 && index<numOfBits){
            if(outsigs==null) outsigs = new LinkedHashMap<Sig, Integer>();
            outsigs.put(sig, index);
        } else System.err.println("Nemoguce uraditi bindOut za Signal "+sig+"!");
        return this;
    }
    public MSignal bindOut(int index, MSignal sig){
        if(index>=0 && index+sig.getNumOfBits()-1<numOfBits){
            if(outsigs==null) outsigs = new LinkedHashMap<Sig, Integer>();
            outsigs.put(sig, index);
        } else System.err.println("Nemoguce uraditi bindOut za MSignal "+sig+"!");
        return this;
    }
    public MSignal bindOut(int startindex, int endindex, Signal[] sig){
        if(startindex>=0 && startindex<numOfBits && endindex>=0 && endindex<numOfBits && startindex<endindex && endindex-startindex+1==sig.length){
            if(outsigs==null) outsigs = new LinkedHashMap<Sig, Integer>();
            for(int i=startindex; i<=endindex; i++)
                outsigs.put(sig[i-startindex], i);
        } else System.err.println("Nemoguce uraditi bindOut za Signal "+sig+"!");
        return this;
    }
    public MSignal bindIn(int index, Signal sig){
        if(index>=0 && index<numOfBits){
            sig.addElement(this);
            if(insigs==null) insigs = new LinkedHashMap<Sig, Integer>();
            insigs.put(sig, index);
        } else System.err.println("Nemoguce uraditi bindIn za Signal "+sig+"!");
        return this;
    }
    public MSignal bindIn(int index, MSignal sig){
        if(index>=0 && index<numOfBits){
            sig.addElement(this);
            if(insigs==null) insigs = new LinkedHashMap<Sig, Integer>();
            insigs.put(sig, index);
        } else System.err.println("Nemoguce uraditi bindIn za MSignal "+sig+"!");
        return this;
    }
    public MSignal bindIn(int startindex, int endindex, Signal[] sig){
        if(startindex>=0 && startindex<numOfBits && endindex>=0 && endindex<numOfBits && startindex<endindex && endindex-startindex+1==sig.length){
            if(insigs==null) insigs = new LinkedHashMap<Sig, Integer>();
            for(int i=startindex; i<=endindex; i++){
                sig[i].addElement(this);
                insigs.put(sig[i], i);
            }
        } else System.err.println("Nemoguce uraditi bindIn za Signal "+sig+"!");
        return this;
    }
    @Override
    public void updateSignal(Sig sig) {
        if(insigs==null) return;
        boolean changed = false;
        Iterator iter = insigs.keySet().iterator();
        while(iter.hasNext()){
            Sig tmp = (Sig)iter.next();
            if(sig==tmp) changed=true;
        }
        if(changed) update(true);
    }
    @Override
    public MSignal addElement(Updateable elem) {
        if(elements==null)
            elements = new ArrayList<Updateable>();
        if(!elements.contains(elem))
            elements.add(elem);
        return this;
    }
    private void applySignalToElements(){
        if(elements==null) return;
        Iterator iter = elements.iterator();
        while(iter.hasNext())
            ((Updateable)iter.next()).updateSignal(this);
    }
}
